package com.xpj.service;

import com.xpj.template.CacheTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor;

@Slf4j
public abstract class AbstractPreloadService<T, ID> implements GenericPreloadService<T, ID> {

    protected static final int[] DEFAULT_BATCH_BASE_TTL = {1800, 2400, 3000, 3600, 4200}; // 30-70分钟

    // 默认重试次数
    protected static final int DEFAULT_RETRY_COUNT = 2;

    // 默认随机偏移范围
    protected static final int DEFAULT_RANDOM_OFFSET_RANGE = 300; // ±5分钟

    protected Executor preloadExecutor;

    private CacheTemplate cacheTemplate;

    public AbstractPreloadService(Executor preloadExecutor, CacheTemplate cacheTemplate){
        this.preloadExecutor = preloadExecutor;
        this.cacheTemplate = cacheTemplate;
    }

    @Override
    public void batchPreloadWithSpread(List<ID> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            log.warn("批量预热ID列表为空");
            return;
        }

        log.info("开始批量分散预热，数据项数量: {}", ids.size());

        // 1. 打乱顺序，避免同类数据集中
        Collections.shuffle(ids);

        // 2. 分成指定数量的批次
        List<List<ID>> batches = splitIntoBatches(ids, getBatchCount());

        // 3. 每批使用不同的基础TTL进行预热
        int[] batchBaseTtl = getBatchBaseTtl();
        for (int i = 0; i < batches.size(); i++) {
            int batchIndex = i;
            List<ID> batch = batches.get(i);

            preloadExecutor.execute(() -> {
                // 每批使用不同的基础TTL
                int baseTtl = batchBaseTtl[batchIndex % batchBaseTtl.length];
                preloadBatch(batch, baseTtl);
            });
        }
    }

    /**
     * 预热同一批次缓存
     * @param ids
     * @param baseTtl
     */
    private void preloadBatch(List<ID> ids, int baseTtl) {
        for (ID id : ids) {
            try {
                // 查询数据（会自动写入缓存）
                T data = loadById(id);
                if (data != null) {
                    // 添加随机TTL偏移，避免同批同时过期
                    int randomOffset = new Random().nextInt(getRandomOffsetRange() * 2) - getRandomOffsetRange();
                    int ttl = baseTtl + randomOffset;

                    // 强制刷新缓存
                    cacheTemplate.forcePut(buildCacheKey(id), data, ttl);

                    log.info("数据预热成功，ID: {}, TTL: {}秒", id, ttl);
                }
            } catch (Exception e) {
                log.error("数据预热失败，ID: {}", id, e);
                // 失败重试
                retryPreload(id, baseTtl);
            }
        }
    }

    @Override
    public void preloadSingle(ID id) {
        if (id == null) {
            log.warn("预热单个数据项ID为空");
            return;
        }
        preloadExecutor.execute(() -> {
            try {
                // 查询数据（会自动写入缓存）
                T data = loadById(id);
                if (data != null) {
                    // 强制刷新缓存
                    cacheTemplate.forcePut(buildCacheKey(id), data, getDefaultTtl());

                    log.info("单个数据预热成功，ID: {}, TTL: {}秒", id, getDefaultTtl());
                }
            } catch (Exception e) {
                log.info("单个数据预热失败，ID: {}, TTL: {}秒", id, getDefaultTtl(), e);
                retryPreload(id, getDefaultTtl());
            }
        });
    }

    /**
     * 预热失败重试
     */
    protected void retryPreload(ID id, int baseTtl) {
        int retryCount = getRetryCount();
        for (int i = 0; i < retryCount; i++) {
            try {
                //退避策略
                Thread.sleep(1000 * (i + 1));

                T data = loadById(id);
                if (data != null) {
                    int randomOffset = new Random().nextInt(getRandomOffsetRange() * 2) - getRandomOffsetRange();
                    int ttl = baseTtl + randomOffset;
                    cacheTemplate.forcePut(buildCacheKey(id), data, ttl);
                    log.info("数据预热重试成功，ID: {}, 重试次数: {}", id, i + 1);
                    return;
                }
            } catch (Exception e) {
                log.error("数据预热重试失败，ID: {}, 重试次数: {}", id, i + 1, e);
            }
        }
    }

    /**
     * 将列表分成指定数量的批次
     */
    private List<List<ID>> splitIntoBatches(List<ID> list, int batchCount) {
        List<List<ID>> batches = new ArrayList<>();
        if (list == null || list.isEmpty()) {
            return batches;
        }

        int totalSize = list.size();
        int batchSize = (int) Math.ceil((double) totalSize / batchCount);

        for (int i = 0; i < batchCount; i++) {
            int start = i * batchSize;
            int end = Math.min(start + batchSize, totalSize);

            if (start < end) {
                batches.add(list.subList(start, end));
            }
        }
        return batches;
    }


    /**
     * 获取默认TTL（秒）
     */
    protected int getDefaultTtl() {
        return 1800; // 默认30分钟
    }

    /**
     * 获取批次数量
     */
    protected int getBatchCount() {
        return 5;
    }

    /**
     * 获取批量预热的基础TTL数组
     */
    protected int[] getBatchBaseTtl() {
        return DEFAULT_BATCH_BASE_TTL;
    }

    /**
     * 获取重试次数
     */
    protected int getRetryCount() {
        return DEFAULT_RETRY_COUNT;
    }

    /**
     * 获取随机偏移范围（秒）
     */
    protected int getRandomOffsetRange() {
        return DEFAULT_RANDOM_OFFSET_RANGE;
    }
}
